package com.ihr360.insurance.service.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.DateUtils;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.vo.PageData;
import com.ihr360.entity.BasicStaffInfoEntity;
import com.ihr360.excel.entity.ExportParams;
import com.ihr360.insurance.convertor.IncreaseMemberPlanConvertor;
import com.ihr360.insurance.dao.IncreaseMemberPlanReposity;
import com.ihr360.insurance.entity.request.MemberPlanReq;
import com.ihr360.insurance.entity.response.LedgerResponse;
import com.ihr360.insurance.entity.snapshot.BenefitSnapshot;
import com.ihr360.insurance.entity.snapshot.CompanyBenefitSnapshot;
import com.ihr360.insurance.entity.vo.BenefitVo;
import com.ihr360.insurance.entity.vo.MemberPlanVo;
import com.ihr360.insurance.entity.vo.StaffBenefitVo;
import com.ihr360.insurance.model.dto.ledger.IncreaseMemberPlan;
import com.ihr360.insurance.model.dto.ledger.MonthlyLedgerSummary;
import com.ihr360.insurance.model.dto.ledger.mapped.MemberPlan;
import com.ihr360.insurance.service.CompanyBenefitService;
import com.ihr360.insurance.service.IncreaseMemberService;
import com.ihr360.insurance.service.MonthlyLedgerService;
import com.ihr360.insurance.service.MonthlyLedgerSummaryService;
import com.ihr360.insurance.service.StaffBenefitService;
import com.ihr360.rest.reposity.SimpleRestReposity;
import com.ihr360.rest.service.AbstractSimpleRestServiceImpl;
import com.ihr360.shared.basicstaffinfo.dto.SyncedBasicStaffInfo;
import com.ihr360.validation.ValidationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @author Stone.Shi
 * @description 增员计划service
 * @date 2018-03-15 14:01:24.
 */
@Service
public class IncreaseMemberServiceImpl extends AbstractSimpleRestServiceImpl<IncreaseMemberPlan, Long>
        implements IncreaseMemberService {

    @Autowired
    private IncreaseMemberPlanReposity reposity;

    @Autowired
    private CompanyBenefitService companyBenefitService;

    @Autowired
    private IncreaseMemberPlanConvertor convertor;

    @Autowired
    private MonthlyLedgerSummaryService ledgerSummaryService;

    @Autowired
    private MonthlyLedgerService ledgerService;

    @Autowired
    private StaffBenefitService staffBenefitService;

    @Override
    public SimpleRestReposity<IncreaseMemberPlan, Long> getSimpleRestReposity() {
        return reposity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LedgerResponse addIncreaseStaffToLedger(String companyId, String userId,
                                                   Integer ledgerYear, Integer ledgerMonth, MemberPlanVo memberPlanVo) {
        validateDate(ledgerYear, ledgerMonth);
        ValidationUtils.rejectIfEmpty(memberPlanVo, "", "要加入台账的员工不能为空");

        List<StaffBenefitVo> staffBenefitVos = staffBenefitService.getAllStaffBenefitInclude(companyId, Lists.newArrayList(memberPlanVo.getStaffId()));
        return ledgerService.addLedgerStaff(companyId, userId, staffBenefitVos, ledgerYear, ledgerMonth);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteIncreaseStaffFromLedger(String companyId, String userId, Integer ledgerYear,
                                                 Integer ledgerMonth, MemberPlanVo memberPlanVo) {
        validateDate(ledgerYear, ledgerMonth);
        ValidationUtils.rejectIfEmpty(memberPlanVo, "", "要从台账移除的员工不能为空");
        ledgerService.deleteLedgerStaff(companyId, userId, ledgerYear, ledgerMonth, Lists.newArrayList(memberPlanVo.getStaffId()));
        return true;
    }

    /**
     * 增员计划表数据仅存在insert delete操作，不存在update操作
     *
     * @param ledgerSummaryId
     * @param authStaffIds
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveIncreaseMemberRecords(Long ledgerSummaryId,
                                          List<String> authStaffIds) {
        MonthlyLedgerSummary ledgerSummary = ledgerSummaryService.getOne(ledgerSummaryId);
        ValidationUtils.rejectIfEmpty(ledgerSummary, null, "台账数据不存在，请刷新后重试");

        String companyId = ledgerSummary.getCompanyId();
        String userId = ledgerSummary.getUserId();

        Integer ledgerYear = ledgerSummary.getLedgerYear();
        Integer ledgerMonth = ledgerSummary.getLedgerMonth();

        MemberPlanReq req = new MemberPlanReq();
        req.setYear(ledgerYear);
        req.setMonth(ledgerMonth);
        req.setPageNo(1);
        //设置最大值，保证获取到全部数据
        req.setPageSize(Integer.MAX_VALUE);
        PageData<MemberPlanVo> increaseMember = getListFromStaffBenefit(companyId, userId, req, authStaffIds);
        List<MemberPlanVo> increaseStaffs = increaseMember.getContent();
        if (CollectionUtils.isEmpty(increaseStaffs)) {
            return;
        }
        //设置年月数据
        increaseStaffs.forEach(item -> {
            item.setUserId(userId);
            item.setYear(ledgerYear);
            item.setMonth(ledgerMonth);
        });
        List<IncreaseMemberPlan> increaseMemberPlans = convertor.convertEntitiesToDtos(increaseStaffs);
        reposity.save(increaseMemberPlans);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteIncreaseMemberRecords(String companyId, String userId,
                                            Integer ledgerYear, Integer ledgerMonth) {
        validateDate(ledgerYear, ledgerMonth);
        reposity.deleteByCompanyIdAndUserIdAndYearAndMonth(companyId, userId, ledgerYear, ledgerMonth);
    }

    @Override
    public PageData<MemberPlanVo> getIncreaseMember(String companyId, String userId,
                                                    MemberPlanReq req, List<String> authStaffIds) {
        validateDate(req.getYear(), req.getMonth());
        boolean ledgerLocked = ledgerSummaryService.isLedgerLocked(companyId, userId, req.getYear(), req.getMonth());
        if (ledgerLocked) {
            return getList(companyId, userId, req, authStaffIds);
        } else {
            return getListFromStaffBenefit(companyId, userId, req, authStaffIds);
        }
    }

    @Override
    public ExportParams<List<Object>> getExportExcelData(String companyId, String userId, MemberPlanReq req, List<String> authStaffIds) {
        validateDate(req.getYear(), req.getMonth());

        ExportParams<List<Object>> exportParams = new ExportParams();
        Map<String, String> headerMap = getExcelHeaderMap();
        exportParams.setHeaderMap(headerMap);

        List<List<Object>> excelData = getExcelData(companyId, userId, req, authStaffIds);
        exportParams.setRowDatas(excelData);

        return exportParams;
    }


    private Map<String, String> getExcelHeaderMap() {
        Map<String, String> headerMap = Maps.newLinkedHashMap();
        headerMap.put(MemberPlan.Field.staff_staffName, "姓名");
        headerMap.put(MemberPlan.Field.staff_departmentName, "部门");
        headerMap.put(MemberPlan.Field.staff_idCardNo, "证件号");
        headerMap.put(MemberPlan.Field.siCompanyBenefit_name, "社保方案");
        headerMap.put(MemberPlan.Field.socialInsurance_startOn, "社保起始缴纳月");
        headerMap.put(MemberPlan.Field.hfCompanyBenefit_name, "公积金方案");
        headerMap.put(MemberPlan.Field.houseFund_startOn, "公积金起始缴纳月");
        headerMap.put(MemberPlan.Field.inLedger, "已在月度台账中");
        return headerMap;
    }

    private List<List<Object>> getExcelData(String companyId, String userId,
                                            MemberPlanReq req, List<String> authStaffIds) {
        req.setPageSize(Integer.MAX_VALUE);
        PageData<MemberPlanVo> increaseMember = getIncreaseMember(companyId, userId, req, authStaffIds);
        List<MemberPlanVo> all = increaseMember.getContent();
        if (CollectionUtils.isEmpty(all)) {
            return Collections.emptyList();
        }
        Map<Long, String> departmentFullNames = req.getDepartmentFullNames();
        if (departmentFullNames == null) {
            departmentFullNames = Maps.newHashMap();
        }
        List<List<Object>> result = Lists.newArrayListWithCapacity(all.size());
        for (MemberPlanVo item : all) {
            List<Object> rowData = Lists.newArrayList();
            rowData.add(item.getStaffName());
            rowData.add(departmentFullNames.get(item.getDepartmentId()));
            rowData.add(item.getIdCardNo());

            CompanyBenefitSnapshot siCompanyBenefit = item.getSiCompanyBenefit();
            rowData.add(siCompanyBenefit != null ? siCompanyBenefit.getName() : "");
            BenefitSnapshot socialInsurance = item.getSocialInsurance();
            rowData.add(socialInsurance != null && socialInsurance.getStartOn() != null
                    ? DateUtils.formatDateStr(socialInsurance.getStartOn(), DateUtils.STANDARD_DATE_FORMAT_YM1) : "");

            CompanyBenefitSnapshot hfCompanyBenefit = item.getHfCompanyBenefit();
            rowData.add(hfCompanyBenefit != null ? hfCompanyBenefit.getName() : "");
            BenefitSnapshot houseFund = item.getHouseFund();
            rowData.add(houseFund != null && houseFund.getStartOn() != null
                    ? DateUtils.formatDateStr(houseFund.getStartOn(), DateUtils.STANDARD_DATE_FORMAT_YM1) : "");
            rowData.add(item.getInLedger() ? "是" : "否");
            result.add(rowData);
        }
        return result;
    }

    private void validateDate(Integer year, Integer month) {
        ValidationUtils.rejectIfEmpty(year, "", "年份不能为空");
        ValidationUtils.rejectIfEmpty(month, "", "月份不能为空");
        ValidationUtils.rejectNotInRange(month, 1, 12, "", "月份无效");
    }

    private PageData<MemberPlanVo> getList(String companyId, String userId, MemberPlanReq req, List<String> authStaffIds) {
        Specification<IncreaseMemberPlan> whereClause = getWhereClause(companyId, userId, req, authStaffIds);
        Page<IncreaseMemberPlan> page = reposity.findAll(whereClause, getPageable(req.getPageNo(), req.getPageSize()));
        return new PageData<>(page.getTotalPages(), page.getTotalElements(), convertor.convertDtosToEntities(page.getContent()));
    }

    private PageData<MemberPlanVo> getListFromStaffBenefit(String companyId, String userId,
                                                           MemberPlanReq req, List<String> authStaffIds) {
        PageData<StaffBenefitVo> page = staffBenefitService.getIncreaseMember(companyId, authStaffIds, req);
        List<String> existedStaffIds = ledgerService.getStaffIds(companyId, userId, req.getYear(), req.getMonth());
        return new PageData<>(page.getTotalPages(), page.getTotalElements(),
                generateVos(page.getContent(), CollectionUtils.isEmpty(existedStaffIds) ? Collections.emptyList() : existedStaffIds));
    }

    private List<MemberPlanVo> generateVos(List<StaffBenefitVo> content, List<String> existedStaffIds) {
        if (CollectionUtils.isEmpty(content)) {
            return Collections.emptyList();
        }
        List<MemberPlanVo> result = Lists.newArrayListWithCapacity(content.size());
        content.forEach(item -> {
            MemberPlanVo vo = new MemberPlanVo();
            BasicStaffInfoEntity staff = item.getStaff();
            vo.setStaffId(staff.getId());
            vo.setStaffName(staff.getStaffName());
            vo.setDepartmentId(staff.getDepartmentId());
            vo.setDepartmentName(staff.getDepartmentName());
            vo.setIdCardNo(staff.getIdCardNo());
            vo.setInLedger(existedStaffIds.contains(staff.getId()));

            BenefitVo si = item.getSocialInsurance();
            if (si != null) {
                vo.setSiCompanyBenefit(new CompanyBenefitSnapshot(si.getCompanyBenefitId(), si.getCompanyBenefitName()));
                vo.setSocialInsurance(new BenefitSnapshot(si.getBase(), si.getStartOn(), si.getEndOn(), si.getCalculatable()));
            }
            BenefitVo hf = item.getHouseFund();
            if (hf != null) {
                vo.setHfCompanyBenefit(new CompanyBenefitSnapshot(hf.getCompanyBenefitId(), hf.getCompanyBenefitName()));
                vo.setHouseFund(new BenefitSnapshot(hf.getBase(), hf.getStartOn(), hf.getEndOn(), hf.getCalculatable()));
            }

            result.add(vo);
        });
        return result;
    }

    /**
     * 增员计划人员仅关注起始缴纳月
     *
     * @param companyId
     * @param req
     * @param authStaffIds
     * @return
     */
    private Specification<IncreaseMemberPlan> getWhereClause(String companyId, String userId,
                                                             MemberPlanReq req, List<String> authStaffIds) {
        return (root, q, cb) -> {

            Predicate predicate = cb.conjunction();
            predicate.getExpressions().add(cb.equal(getPath(root, MemberPlan.Field.companyId).as(String.class), companyId));
            predicate.getExpressions().add(cb.equal(getPath(root, MemberPlan.Field.userId).as(String.class), userId));
            predicate.getExpressions().add(cb.equal(getPath(root, MemberPlan.Field.year).as(Integer.class), req.getYear()));
            predicate.getExpressions().add(cb.equal(getPath(root, MemberPlan.Field.month).as(Integer.class), req.getMonth()));
            predicate.getExpressions().add(getPath(root, MemberPlan.Field.staff_id).as(String.class).in(authStaffIds));

            if (CollectionUtils.isNotEmpty(req.getSiCompanyBenefitIds())) {
                predicate.getExpressions().add(getPath(root, MemberPlan.Field.siCompanyBenefit_id).as(String.class)
                        .in(req.getSiCompanyBenefitIds()));
            }

            if (CollectionUtils.isNotEmpty(req.getHfCompanyBenefitIds())) {
                predicate.getExpressions().add(getPath(root, MemberPlan.Field.hfCompanyBenefit_id).as(String.class)
                        .in(req.getHfCompanyBenefitIds()));
            }

            Join<IncreaseMemberPlan, SyncedBasicStaffInfo> staff = root.join(MemberPlan.Field.staff);
            predicate.getExpressions().add(cb.equal(staff.get(SyncedBasicStaffInfo.Field.companyId).as(String.class), companyId));

            if (StringUtils.isNotBlank(req.getStaffName())) {
                predicate.getExpressions().add(
                        cb.like(staff.get(SyncedBasicStaffInfo.Field.staffName).as(String.class), req.getStaffName() + "%"));
            }

            if (StringUtils.isNotBlank(req.getIdCardNo())) {
                predicate.getExpressions().add(
                        cb.like(staff.get(SyncedBasicStaffInfo.Field.idCardNo).as(String.class), req.getIdCardNo() + "%"));
            }

            if (CollectionUtils.isNotEmpty(req.getDepartmentIds())) {
                predicate.getExpressions().add(
                        staff.get(SyncedBasicStaffInfo.Field.departmentId).as(Long.class).in(req.getDepartmentIds()));
            }

            q.where(predicate);
            //仅支持员工的基本信息数据排序
            Order order = getOrder(req.getSort(), staff, cb);
            if (order != null) {
                q.orderBy(order);
            }

            return q.getRestriction();
        };
    }

    private static final String ORDER_ASC = "ASC";

    /**
     * 仅处理单属性字段排序
     *
     * @param sort
     * @param root
     * @param cb
     */
    private Order getOrder(String sort, Path root, CriteriaBuilder cb) {
        if (StringUtils.isBlank(sort)) {
            return null;
        }
        String[] o = sort.split(",");
        if (o.length == 1) {
            return cb.asc(getPath(root, o[0]));
        } else if (o.length == 2) {
            if (ORDER_ASC.equalsIgnoreCase(o[1])) {
                return cb.asc(getPath(root, o[0]));
            } else {
                return cb.desc(getPath(root, o[0]));
            }
        }
        return null;
    }

    private Pageable getPageable(Integer pageNo, Integer pageSize) {
        int page = pageNo == null ? 0 : pageNo;
        if (page > 0) {
            page--;
        }
        return new PageRequest(page, pageSize == null ? 0 : pageSize);
    }

    private Path<IncreaseMemberPlan> getPath(Path<IncreaseMemberPlan> root, String attributeName) {
        String[] attributes = attributeName.split("\\.");
        Path<IncreaseMemberPlan> path = root;
        for (String attribute : attributes) {
            path = path.get(attribute);
        }
        return path;
    }
}
